跳至主要内容

仿一個掘金APP會員等級輪播

· 閱讀時間約 5 分鐘
Imagine Chiu
Front End Engineer @ Bearests

使用的是我開源庫 bear-react-carousel

主要試試看能做到什麼程度,之前公司也有類似的需求

廢話不多說,直接開始

需求

首先我們先看掘金在APP端的預覽結果

Juejin member level carousel

  • 可滑動的部分只有 卡片的部分
  • 並且在滑動時等級卡片會往上移動
  • 等級卡片顯示為置中為 選取項目
  • 等級卡下方需要被遮住(圓弧),不顯示完全
  • 等級名稱項目需同步移動
  • 等級名稱項目移動時圓弧移動
  • 等級名稱線需同步移動
  • API 撈回時,預設需要移動到選取的等級(無動畫移動)

开发輪播項目

輪播項目可切分為:

  • 等級卡片,顯示1.2個並且置
<BearCarousel
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>
  • 等級名稱
  • 水平線 兩者都應顯示 3 並居中。
<BearCarousel
slidesPerView={3}
isCenteredSlides={true}
// ...ignore
/>

所以開發目標是在移動等級卡片時同步等級名稱等級線的移動。

import React, {useCallback, useEffect, useRef, useState} from 'react';
import BearCarousel, {TMoveEffectFn, TBearSlideItemDataList, BearSlideCard, elClassName, Controller, TOnSlideChange} from 'bear-react-carousel';

const MemberLevelWrapper = () => {
const carouselMainRef = useRef<BearCarousel>(null);
const carouselMetaRef = useRef<BearCarousel>(null);
const carouselLineRef = useRef<BearCarousel>(null);

return <>
{/* Level Card */}
<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef, carouselLineRef]}
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>

{/* Level Name*/}
<BearCarousel
ref={carouselMetaRef}
// ...ignore
/>

{/* Level Line */}
<BearCarousel
ref={carouselLineRef}
// ...ignore
/>
</>;
};

接下來,滾動時,等級卡片會向上移動,我們需要實現一個新的動畫效果功能

import BearCarousel, {TMoveEffectFn} from 'bear-react-carousel';

const mainMoveEffectFn: TMoveEffectFn = useCallback((percentageInfo) => {
const transformY = 40;
return {
transform: `translate(0px, ${-transformY * (percentageInfo.calcPercentage - 1)}px)`,
};
}, []);

<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef, carouselLineRef]}
moveEffect={{
moveFn: mainMoveEffectFn,
}}
// ...ignore
/>

所以開發目標是 移動等級卡時,同步移動 等級名稱與等級線

const levelNameMoveEffectFn: TMoveEffectFn = useCallback((percentageInfo) => {
const transformY = -19;
return {
transform: `translate(0px, ${-transformY * (percentageInfo.calcPercentage - 1)}px)`,
};
}, []);

<BearCarousel
ref={carouselMainRef}
moveEffect={{
moveFn: levelNameMoveEffectFn,
}}
// ...ignore
/>

至於圓弧的部分,我們最後使用遮罩的方式來顯示,並且關閉 NavButton、Pagination、MouseMove,畫SVG不熟的話,可以使用這個工具

svg-path-editor

SVG Mask for level line

<LevelLine>
<LineBearCarousel
ref={carouselLineRef}
data={lineData}
slidesPerView={3}
isCenteredSlides={true}
isEnableNavButton={false}
isEnablePagination={false}
isEnableMouseMove={false}
/>

<svg height="100%" width="100%">
<clipPath id="wave12">
{/*跟隨線*/}
<path d="M 0 4 C 175 30 175 30 356 4 L 356 2 C 175 28 175 28 0 2" stroke="black" fill="transparent"/>
</clipPath>
</svg>
</LevelLine>

SVG Mask Result fro level line

而 Level Card 遮罩的部分一樣,但我用了3塊

SVG Mask 1 for Level Card SVG Mask 2 for Level Card SVG Mask 3 for Level Card

<svg height="100%" width="100%">
<clipPath id="wave10">
<path d="M 0,0 356,0 356,130 0,130" stroke="black" fill="transparent"/>
{/* 圓弧 */}
<path d="M 0 130 C 175 155 175 155 356 130" stroke="black" fill="transparent"/>
{/* 下箭頭 */}
<path d="M 152 143 L 176 153 L 178 153 L 202 143" stroke="black" fill="transparent"/>
</clipPath>
</svg>

SVG Mask Result for Level Card

最後就是 預設選取等級的部分(無動畫方式移動)

const [carouselMainController, setMainController] = useState<Controller>();
const [currLevel, setCurrLevel] = useState<{lv: number,count: number}|undefined>();


useEffect(() => {
carouselMainController?.slideToPage(5, false);
}, [carouselMainController]);


<BearCarousel
ref={carouselMainRef}
syncCarouselRefs={[carouselMetaRef]}
onSlideChange={handleSlideChange}
setController={setMainController}
slidesPerView={1.2}
spaceBetween={20}
isCenteredSlides={true}
// ...ignore
/>

完成結果

Finished Demo

以上就完成了,基本上如果兩邊都需要移動的話,互相同步在(bear-react-carousel)[https://bear-react-carousel.pages.dev] 因為是進行獨立的同步控制,所以不會循環互控,但也因為這樣所以目前是無法A控制B , B 自動再控制C,所以在這邊可以發現是A控制B和C。